// 命令模式的作用: 将执行特定任务的对象与调用该方法的对象解耦

// 假定我们有一个在线订单系统, 用户可以下单, 跟踪订单, 取消订单

class OrderManager {
    private orders:any[]
    constructor() {
        this.orders = [];
    }

    // 下订单
    placeOrder(order, id) {
        this.orders.push(id);
        const msg = `您已成功的下单了 ${order}(${id})`;
        console.log(msg);
        return msg;
    }

    // 跟踪订单
    traceOrder(id) {
        const msg = `你的订单 ${id} 将在20分钟内到达`;
        console.log(msg);
        return msg;
    }

    cancelOrder(id) {
        this.orders = this.orders.filter(order => order.id !== id);
        const msg = '您的订单已取消';
        console.log(msg);
        return msg;
    }
};

const manager = new OrderManager();
manager.placeOrder('鸡蛋', '123');
manager.traceOrder('123');
manager.cancelOrder('123');

// https://juejin.cn/post/7208046833330225212#heading-10
// https://www.patterns.dev/vanilla/command-pattern


//  上述实现的方式存在以下问题: 当我们想重命名函数, 或者修改函数内部实现时, 会不太方便
// 举个例子, 当我们想把 placeOrder 修改成addOrder时, 那么在所有使用managerOrder的地方, 都要把placeOrder 修改成addOrder


//缺点: 命令模式会增加不必要的代码

// 所以我们可以改造一下, 让orderManager只执行命令

class orderManager {
    private orders:any[]
    constructor() {
        this.orders = [];
    }
    execute(command, ...args) {
        return command.execute(this.orders, ...args);
    }
}

class Command {
    private execute:any
    constructor(execute) {
        this.execute = execute;
    }
}

function PlaceOrderCommand(order, id) {
    return new Command((orders) => {
        orders.push(id);
        console.log(`您已成功的下了订单 ${order}(${id})`);
    });
}

function CancelOrderCommand(id) {
    return new Command((orders) => {
        orders = orders.filter((order) => order.id !== id);
        console.log(`您已成功取消了订单 ${id}`);
    });
}

function TraceOrderCommand() {
    return new Command(() => {
        console.log(`您的订单将在10分钟内送达`);
    });
}

const manager2 = new orderManager();
manager2.execute(PlaceOrderCommand('鸡蛋', '123'));
manager2.execute(TraceOrderCommand());
manager2.execute(CancelOrderCommand(123));


// 来看一下命令模式的意图：
// 将一个请求封装为一个对象，从而使你可用不同的请求对客户进行参数化；对请求排队或记录请求日志，以及支持可撤消的操作。

// 适用场景
// （A）抽象出待执行的动作以参数化某对象

// 命令模式将待执行的动作封装为命令对象，我们可以轻松的将命令作为方法的参数进行传递、将命令对象保存在其他对象中、在运行时切换对象拥有的命令以此来改变对象的行为。

// （B）延迟执行请求
// 命令对象并不依赖于调用者，这意味着命令对象和调用者可以拥有完全独立的生命周期。调用者在调用命令对象的执行方法后，可能就已经被回收，但命令对象可以仍在执行请求。借助于这个特点，可以轻松实现延迟任务。

// （C）把请求放入队列中
// 把请求放入队列中，我们可以得到如下好处：
// 对请求进行排队：借助于队列的先进先出特征，我们可以实现对到达的请求进行排队执行；
// 对请求回滚：记录下所有操作并按先后顺序对其进行排列，可轻松实现多个回滚操作；

// （D）支持撤销操作
// 可在execute()调用之前，存储接收者的状态，在撤销操作时，用存储的状态来恢复接收者到调用execute()之前的状态。此时，Command 接口需要添加一个unExecute()操作，该操作用来取消上一个execute()调用产生的影响。当执行命令被存储在一个历史列表中时，我们就可以进行连续撤销的操作。